package main.labone;

import java.math.BigDecimal;

import static java.lang.Math.abs;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;

/**
 * Created with IntelliJ IDEA.
 * User: Борис
 * Date: 08.10.13
 * Time: 23:02
 * To change this template use File | Settings | File Templates.
 */
public class LabOne {
    private int vector[];  // вектор перестановок
    private double matrixOfSystemEx[][];   // расширенная матрица системы
    private double copyMatrixOfSystem[][];
    private double[] B; // столбец свободных членов
    private double[] B1; // проверка
    private double[][] matrixOfSystem; // матрица системы
    private double[][] inverseMatrix;
    private double[][] checkInverseMatrix;
    private double[] X;
    private double[] xCramers;
    private double normOneMatrixOfSystem;
    private double normTwoMatrixOfSystem;
    private double normCMatrixOfSystem;
    private double normOneInverseMatrix;
    private double normTwoInverseMatrix;
    private double normCInverseMatrix;
    private double conditionNumberOne;
    private double conditionNumberTwo;
    private double conditionNumberThree;
    private double detMatrixOfSystem = 0;
    private int strings;   // число строк матрицы copyMatrixOfSystem[][]

    public double getDetMatrixOfSystem(){
        return detMatrixOfSystem;
    }

    public LabOne(double matrixOfSystemEx[][]){
        this.matrixOfSystemEx = matrixOfSystemEx;
        this.copyMatrixOfSystem = copyMatrix(matrixOfSystemEx);
        int sizeVector;
        strings = sizeVector = matrixOfSystemEx.length;
        vector = new int[sizeVector];
        for(int i = 0; i<sizeVector; i++)
            vector[i] = i+1;
        matrixOfSystem = new double[strings][strings];
        for(int i = 0; i<strings; i++)
            for(int j = 0; j<strings; j++){
                matrixOfSystem[i][j] = matrixOfSystemEx[i][j];
            }
        B = new double[strings];
        for(int i = 0; i<strings; i++)
            B[i] = matrixOfSystemEx[i][strings];
        B1 = new double[B.length];
        X = new double[strings];
    }
    //создат копию расширинной матрицы системы и возвращает ссылку на неё
    private double[][] copyMatrix(double matrixOfSystem[][]){
        int strings = matrixOfSystem.length;   // число строк
        int columns = strings+1;
        double copyMatrix[][] = new double[strings][columns];
        for(int i = 0; i<strings; i++)
            for(int j = 0; j<columns; j++){ // число столбцов на единицу больше, чем число строк
                copyMatrix[i][j] = matrixOfSystem[i][j];
            }
        return copyMatrix;
    }
    // меняет первый столбец и столбец с индексом index местами
    private void swapColumn(int index0, int index1){
        double swap;
        for(int i = 0; i<strings; i++){
            swap = this.copyMatrixOfSystem[i][index0];
            this.copyMatrixOfSystem[i][index0] = this.copyMatrixOfSystem[i][index1];
            this.copyMatrixOfSystem[i][index1] = swap;
        }
    // изменяю вектор перестановок
        int numCol;
        numCol = vector[index0];
        vector[index0] = vector[index1];
        vector[index1] = numCol;
    }
    // меняет первую строку и строку с индексом index местами
    private void swapString(int index0,int index1){
        int columns = copyMatrixOfSystem.length+1; // число столбцов
        double swap;
        for(int i = 0; i<columns; i++){
            swap = copyMatrixOfSystem[index0][i];
            copyMatrixOfSystem[index0][i] = copyMatrixOfSystem[index1][i];
            copyMatrixOfSystem[index1][i] = swap;
        }
    }
    // ищет главный элемент в матрице и перестанавливает его на первое место
    private void mainElementOfMatrix(int index){
        int iMax = index, jMax = index;
        int strings = copyMatrixOfSystem.length;             // число строк
        double max = abs(copyMatrixOfSystem[index][index]);
        for(int i = index; i<strings; i++)
            for(int j = index; j<strings; j++){
                if (abs(copyMatrixOfSystem[i][j])>max){
                    max = abs(copyMatrixOfSystem[i][j]);
                    iMax = i;
                    jMax = j;
                }
            }
        System.out.printf("\n%s %.2f %s \n","главный элемент",copyMatrixOfSystem[iMax][jMax]," i = "+(iMax+1)+" j = "+(jMax+1));
        if (iMax!=index) swapString(iMax,index);
        if (jMax!=index) swapColumn(jMax,index);
    }
    // складывает две строки
    private void sumLine(double string1[], double string2[]){
        for(int i = 0; i<string1.length; i++){
            string2[i] += string1[i];
        }
    }
    //умножает каждый элемент line на -a
    private double[] multiply(double line[],double a){
        int size = line.length;
        if (a!=0) a = -a;
        double result[] = new double[size];
        for(int i = 0; i<size; i++)
            result[i] = line[i] * a;
        return result;
    }
    // возвращает копию строки line
    private double[] copyLine(double[] line){
        int size = line.length;
        double[] copyLine = new double[size];
        for(int i = 0; i<size; i++){
            copyLine[i] = line[i];
        }
        return copyLine;
    }
    // разделяет первую строку на первый элемент и обращает в нуль все элементы, находящиеся под первым
    private void stageDown(int index){
        int size = copyMatrixOfSystem[0].length; // длина строки
        double mainElement = copyMatrixOfSystem[index][index];
        double[] line1;
        for(int i = 0; i<size; i++)
            copyMatrixOfSystem[index][i] /= mainElement;
        showMatrixAndVector();
        line1 = copyLine(copyMatrixOfSystem[index]);
        for(int i = index; i<size-2; i++){
            sumLine(multiply(line1,copyMatrixOfSystem[i+1][index]),copyMatrixOfSystem[i+1]);
            showMatrixAndVector();
        }
    }
    // прямой ход
    private void down(){
        int it = this.copyMatrixOfSystem.length;
        for(int i = 0; i<it; i++){
            mainElementOfMatrix(i);
            showMatrixAndVector();
            stageDown(i);
        }
    }

    private void stageUp(int index){
        double[] line1 = copyLine(copyMatrixOfSystem[index]);
        for(int i = index; i>0; i--){
            sumLine(multiply(line1,copyMatrixOfSystem[i-1][index]),copyMatrixOfSystem[i-1]);
            showMatrixAndVector();
        }
    }
    // обратный ход
    private void up(){
        int size = copyMatrixOfSystem.length;
        for(int i = size-1; i>0; i--){
            stageUp(i);
        }
    }
    // вычисление неизвестных системы уравнений методом Гаусса
    public void calculate(){
        down();
        up();
        for(int i = 0; i<strings; i++){
            X[vector[i]-1] = copyMatrixOfSystem[i][strings];
        }
    }

    private void showMatrixAndVector(){
        System.out.println();
        int columns = strings+1;
        for(int i = 0; i<strings; i++){
            for(int j = 0; j<columns; j++)
                System.out.printf("%.2f  ",copyMatrixOfSystem[i][j]);
            System.out.println();
        }
        System.out.print("Вектор перестановок: ");
        for(int i = 0; i<vector.length; i++)
            System.out.print(vector[i]+" ");
        System.out.println();
    }

    void showMatrix(String matrix){
        switch(matrix){
            case "matrixOfSystemEx":
                int columns = strings+1;
                for(int i = 0; i<strings; i++){
                    for(int j = 0; j<columns; j++){
                        System.out.printf("%.3f  ",copyMatrixOfSystem[i][j]);
                    }
                    System.out.println();
                }
                break;
            case "matrixOfSystem":
                for(int i = 0; i<matrixOfSystem.length; i++){
                    for(int j = 0; j<matrixOfSystem.length; j++)
                        System.out.printf("%.3f  ",matrixOfSystem[i][j]);
                    System.out.println();
                }
                break;
            case "inverseMatrix":
                System.out.println();
                int size = inverseMatrix.length;
                for(int i = 0; i<size; i++){
                    for(int j = 0; j<size; j++){
                        System.out.printf("%.3f  ",inverseMatrix[i][j]);
                    }
                    System.out.println();
                }
                break;
            case "checkInverseMatrix":
                System.out.println();
                int n = inverseMatrix.length;
                for(int i = 0; i<n; i++){
                    for(int j = 0; j<n; j++){
                        System.out.printf("%.3f  ",checkInverseMatrix[i][j]);
                    }
                    System.out.println();
                }
                break;
            case "B":
                for(int i = 0; i<B.length; i++){
                    System.out.printf("%.3f\n",B[i]);
                }
                break;
            case "B1":
                for(int i = 0; i<B1.length; i++){
                    System.out.println(B1[i]);
                }
                break;
            case "answer":
                for(int i = 0; i<strings; i++){
                    int index = i+1;
                    System.out.printf("%s %.3f\n","X"+index+" = ",X[i]);
                }
                break;
            case "answerCramers":
                for(int i = 0; i<strings; i++){
                    int index = i+1;
                    System.out.printf("%s %.3f\n","X"+index+" = ",xCramers[i]);
                }
                break;
        }
    }
    // проверка решения
    void check(){
        for(int i = 0; i<matrixOfSystem.length; i++){
            double sum = 0;
            for(int j = 0; j<matrixOfSystem.length; j++)
            sum += matrixOfSystem[i][j]*X[j];
            B1[i] = sum - B[i];
        }
    }
    // возвращает минор матрицы matrix (i1 и j1 вычеркнуты)
    private double[][] getMinorMatrix(double[][] matrix,int i1, int j1){
        int size = matrix.length;
        double[][] copyMOfSystem = new double[size][size]; // копия матрицы системы
        for(int i = 0; i<size; i++){
            for(int j = 0; j<size; j++){
                copyMOfSystem[i][j] = matrix[i][j];
            }
        }
        for(int i = i1; i<size-1; i++)
            for(int j = 0; j<size; j++){
                copyMOfSystem[i][j] = copyMOfSystem[i+1][j];
            }
        double[][] matrixA = new double[size-1][size]; // вспомогательная матрица

        for(int i = 0; i<size-1; i++)
            for(int j = 0; j<size; j++){
                matrixA[i][j] = copyMOfSystem[i][j];
            }

        for(int i = 0; i<matrixA.length; i++)
            for(int j = j1; j<size-1; j++){
                matrixA[i][j] = matrixA[i][j+1];
            }

        double[][] minor = new double[size-1][size-1];
        for(int i = 0; i<minor.length; i++)
            for(int j = 0; j<minor.length; j++){
                minor[i][j] = matrixA[i][j];
            }

        return minor;
    }

    private void swapColumn(double[][] matrix,int index){
        double swap;
        for(int i = 0; i<strings; i++){
            swap = matrix[i][index];
            matrix[i][index] = matrix[i][strings];
            matrix[i][strings] = swap;
        }
    }
    // "удаляет" последний столбец из matrixIn
    private double[][] lastColumn(double[][] matrixIn){
        double[][] matrixOut = new double[strings][strings];
        for(int i = 0; i<strings; i++)
            for(int j = 0; j<strings; j++)
                matrixOut[i][j] = matrixIn[i][j];
        return matrixOut;
    }
    // решение системы линейных уравнений методом Крамера
    void methodCramers(){
        double[] X = new double[strings];
        xCramers = new double[strings];
        for(int i = 0; i<strings; i++){
            double[][] copyMatrix = copyMatrix(matrixOfSystemEx);
            swapColumn(copyMatrix,i);
            double[][] matrix = lastColumn(copyMatrix);
            X[i] = determinantMatrixSizeFour(matrix);
            xCramers[i] = X[i]/detMatrixOfSystem;
        }
    }
    // возвращает определитель матрицы 4х4 matrix
    double determinantMatrixSizeFour(double[][] matrix){
        double minor;
        double det = 0;
        for(int i = 0; i<4; i++){
            minor = determinantSizeThree(getMinorMatrix(matrix,0,i));
            det += pow(-1,i)*matrix[0][i]*minor;
        }
        return det;
    }

    // вычисляет определитель матрицы системы
    double getDeterminantMatrixOfSystem(){
        this.detMatrixOfSystem = determinantMatrixSizeFour(matrixOfSystem);
        return this.detMatrixOfSystem;
    }
    // вычисляет матрицу, обратную к матрице системы
    void inverseMatrix(){
        int size = matrixOfSystemEx.length;
        this.inverseMatrix = new double[size][size];
        for(int j = 0; j<size; j++){
            for(int i = 0; i<size; i++)
                inverseMatrix[j][i] = pow(-1,i+j)*determinantSizeThree(getMinorMatrix(matrixOfSystem,i,j))/detMatrixOfSystem;
        }
    }
    // метод вычисляет определитель матрицы 2х2
    private double determinantSizeTwo(double[][] matrix){
        return matrix[0][0]*matrix[1][1]-matrix[1][0]*matrix[0][1];
    }
    // метод вычисляет определитель матрицы 3х3
    private double determinantSizeThree(double[][] matrix){
        double[][] matrixTwoByTwoLeft, matrixTwoByTwoCenter, matrixTwoByTwoRight;
        matrixTwoByTwoLeft = getTwoByTwoMatrixLeft(matrix);
        matrixTwoByTwoCenter = getTwoByTwoMatrixCenter(matrix);
        matrixTwoByTwoRight = getTwoByTwoMatrixRight(matrix);
        return matrix[0][0]*determinantSizeTwo(matrixTwoByTwoLeft)-matrix[0][1]*determinantSizeTwo(matrixTwoByTwoCenter)+matrix[0][2]*determinantSizeTwo(matrixTwoByTwoRight);
    }
    /*
     метод возвращает матрицу 2x2, когда вычеркнут левый столбец
     matrix[] - матрица 3х3
     */
    private double[][] getTwoByTwoMatrixLeft(double[][] matrix){
        double[][] matrixTwoByTwo = new double[2][2];
        matrixTwoByTwo[0][0] = matrix[1][1];
        matrixTwoByTwo[0][1] = matrix[1][2];
        matrixTwoByTwo[1][0] = matrix[2][1];
        matrixTwoByTwo[1][1] = matrix[2][2];
        return matrixTwoByTwo;
    }
    /*
     метод возвращает матрицу 2x2, когда вычеркнут центральный столбец
     matrix[] - матрица 3х3
     */
    private double[][] getTwoByTwoMatrixCenter(double[][] matrix){
        double[][] matrixTwoByTwo = new double[2][2];
        matrixTwoByTwo[0][0] = matrix[1][0];
        matrixTwoByTwo[0][1] = matrix[1][2];
        matrixTwoByTwo[1][0] = matrix[2][0];
        matrixTwoByTwo[1][1] = matrix[2][2];
        return matrixTwoByTwo;
    }
    /*
     метод возвращает матрицу 2x2, когда вычеркнут правый столбец
     matrix[] - матрица 3х3
     */
    private double[][] getTwoByTwoMatrixRight(double[][] matrix){
        double[][] matrixTwoByTwo = new double[2][2];
        matrixTwoByTwo[0][0] = matrix[1][0];
        matrixTwoByTwo[0][1] = matrix[1][1];
        matrixTwoByTwo[1][0] = matrix[2][0];
        matrixTwoByTwo[1][1] = matrix[2][1];
        return matrixTwoByTwo;
    }
    // округление числа х, accuracy число знаков после запятой
    static double round(double x, int accuracy){
        BigDecimal num = new BigDecimal(x);
        num = num.setScale(accuracy,BigDecimal.ROUND_HALF_UP);
        return num.doubleValue();
    }
    // проверка обратной матрицы
    void setCheckInverseMatrix(){
        int size = matrixOfSystem.length;
        checkInverseMatrix = new double[size][size];
        double sum = 0;
        for(int k=0; k<size; k++){
            for(int j=0; j<size; j++){
                for (int i=0; i<size; i++){
                    sum += matrixOfSystem[k][i]*inverseMatrix[i][j];
                    checkInverseMatrix[k][j]=sum;
                }
                sum=0;
            }
            sum=0;
        }
    }

    double getNormOneMatrixOfSystem(){
        normOneMatrixOfSystem = normOne(matrixOfSystem);
        return normOneMatrixOfSystem;
    }

    double getNormOneInverseMatrixOfSystem(){
        normOneInverseMatrix = normOne(inverseMatrix);
        return normOneInverseMatrix;
    }

    double getNormTwoMatrixOfSystem(){
        normTwoMatrixOfSystem = normTwo(matrixOfSystem);
        return normTwoMatrixOfSystem;
    }

    double getNormTwoInverseMatrixOfSystem(){
        normTwoInverseMatrix = normTwo(inverseMatrix);
        return normTwoInverseMatrix;
    }

    double getNormCMatrixOfSystem(){
        normCMatrixOfSystem =  normC(matrixOfSystem);
        return normCMatrixOfSystem;
    }

    double getNormCInverseMatrixOfSystem(){
        normCInverseMatrix =  normC(inverseMatrix);
        return normCInverseMatrix;
    }

    private double normC(double[][] matrix){
        double sum = 0;
        for(int i = 0; i<strings; i++){
            sum += abs(matrix[0][i]);
        }
        double max = sum;
        sum = 0;
        for(int i = 1; i<strings; i++){
            for(int j = 0; j<strings; j++){
                sum += abs(matrix[i][j]);
                }
            if (sum>max){
                max = sum;
            }
            sum = 0;
        }
        return max;
    }

    private double normTwo(double[][] matrix){
        double sum = 0;
        for(int i = 0; i<strings; i++){
            for(int j = 0; j<strings; j++){
                sum += matrix[i][j]*matrix[i][j];
            }
        }
        return sqrt(sum);
    }

    private double normOne(double[][] matrix){
        double sum = 0;
        for(int i = 0; i<strings; i++){
            sum += abs(matrix[i][0]);
        }
        double max = sum;
        sum = 0;
        for(int i = 1; i<strings; i++){
            for(int j = 0; j<strings; j++){
                sum += abs(matrix[j][i]);
            }
            if (sum > max){
                max = sum;
            }
            sum = 0;
        }
        return max;
    }

    void setNumberConditionMatrixOfSystem(){
        conditionNumberOne = normOneMatrixOfSystem*normOneInverseMatrix;
        conditionNumberTwo = normTwoMatrixOfSystem*normTwoInverseMatrix;
        conditionNumberThree = normCMatrixOfSystem*normCInverseMatrix;
    }

    double getConditionNumberOne(){
        return conditionNumberOne;
    }

    double getConditionNumberTwo(){
        return conditionNumberTwo;
    }

    double getConditionNumberThree(){
        return conditionNumberThree;
    }
}
